//============================================================================
// Copyright (C) 2008-2013 Brett R. Jones 
//
// Issued to public domain 2013
//
// brettjones1900@gmail.com
// http://www.p2panarchy.com
//============================================================================
#include "VxNetLib.h"
#include "VxSktBase.h"
#include "VxResolveHost.h"
#include <VxUtilLib/VxParse.h>
#include <VxUtilLib/VxSktUtil.h>
#include <VxUtilLib/VxCommon.h>
#include <VxUtilLib/VxPktHdr.h>
#include <VxUtilLib/vx_debug.h>
#include <time.h>
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>

#ifdef WIN_PLATFORM
	#include "winsock2.h"
	#include "Mswsock.h"
	#define EINPROGRESS	WSAEWOULDBLOCK 
#else
	//#include <fcntl.h>
	#include <sys/ioctl.h>
#endif // WIN_PLATFORM

//============================================================================
//=== Declaration of functions used only in this file ===//
//============================================================================
//! thread function to receive data from socket
static U32 SktBaseRecieveThreadFunc( void * pvContext );
//============================================================================
int VxSktBase::m_iSktCnt = 0;
//============================================================================
//=== constructor    ===//
VxSktBase::VxSktBase()
    : VxSktBuf()
    , VxSktThrottle()
    , m_bIsWebSkt( false )
    , m_oSocket( INVALID_SOCKET )			// handle to socket
    , m_oLclIp()
    , m_oRmtIp()
	, m_bIsConnected( false )				// return true if is connected
	, m_iConnectTimeout(0)					// how long to try to connect
    , m_eSktType( eSktTypeNone )            		// type of socket
	, m_eSktCallbackReason( eSktCallbackReasonUnknown )	// why callback is being performed
    , m_rcLastError(0)
    , m_u32RxLastActiveTime(0)
    , m_u32TxLastActiveTime(0)
    , m_u32ToDeleteTime(0)
    , m_iSktId(0)				// socket unique id
	, m_pfnRecieve(0)
    , m_pfnTransmit(0)
	, m_bClosingFromRxThread(false) // if true then call to close function was made by receive thread
	, m_bClosingFromDestructor(false)
	, m_poSktMgr(0)
	, m_iLastRxLen(0)
	, m_iLastTxLen(0)

	//=== user vars ===//
	, m_pvUserData(0)
	, m_u64AppData(0)

	//=== proxy vars ===//
	, m_eConnectionType(eConnectionTypeDirect)
	, m_poChainedSkt(NULL)
    , m_iRefCnt(0)
{
	if( 0 == m_iSktCnt )
	{
		// first socket being created
		// initialize sockets
		//VxSocketsStartup();
	}
	m_iSktCnt++;
	m_iSktId = m_iSktCnt;
	m_strLclIp = "0.0.0.0";
	m_oLclIp.setToInvalid();
	m_strRmtIp = "0.0.0.0";
	m_oRmtIp.setToInvalid();
#ifdef DEBUG_SKTS
	log_msg( LOG_SKT,  "skt %d created\n", m_iSktId );
#endif // DEBUG_SKTS
}

//============================================================================
//=== destructor ===//
VxSktBase::~VxSktBase()
{
	m_bIsConnected = false;
	m_bClosingFromDestructor = true;
	CloseSkt( 1000 + m_iSktId, true );
	m_oSktRxThread.isAborted();
	m_oSktRxThread.KillThread();

    m_iRefCnt--;
#ifdef DEBUG_SKTS
	log_msg( LOG_SKT,  "skt %d destroyed\n", m_iSktId );
#endif // DEBUG_SKTS
}

//============================================================================
bool VxSktBase::toSocketAddrInfo(	int sockType, 
									const char *addr, 
									int port, 
									struct addrinfo **addrInfo, 
									bool isBindAddr )
{
	struct addrinfo hints;

	memset(&hints, 0, sizeof(addrinfo));
	hints.ai_socktype = sockType;
	hints.ai_flags= AI_NUMERICHOST | AI_PASSIVE;
	char portStr[32];
	sprintf(portStr, "%d", port);
	if( 0 != getaddrinfo( addr, portStr, &hints, addrInfo ))
	{
		m_rcLastError = VxGetLastError();
		log_msg( 0, "VxSktBase::toSocketAddrInfo: error %d %s\n", 
			m_rcLastError,
			DescribeSktError( m_rcLastError ) );
		return false;
	}
	if (isBindAddr == true)
	{
		return true;
	}
	hints.ai_family = (*addrInfo)->ai_family;
	freeaddrinfo(*addrInfo);
	if (getaddrinfo(NULL, portStr, &hints, addrInfo) != 0)
	{
		m_rcLastError = VxGetLastError();
		log_msg( LOG_ERROR, "VxSktBase::toSocketAddrInfo: error %d %s\n",
			m_rcLastError,
			DescribeSktError( m_rcLastError ) );
		return false;
	}
	return true;
}

//============================================================================
bool VxSktBase::toSocketAddrIn(	const char *addr, 
								int port, 
								struct sockaddr_in *sockaddr, 
								bool isBindAddr )
{
	memset(sockaddr, 0, sizeof(sockaddr_in));

	sockaddr->sin_family = AF_INET;
	sockaddr->sin_addr.s_addr = htonl(INADDR_ANY);
	sockaddr->sin_port = htons((unsigned short)port);

	if( true == isBindAddr ) 
	{
		sockaddr->sin_addr.s_addr = inet_addr(addr);
		if (sockaddr->sin_addr.s_addr == INADDR_NONE) 
		{
			struct hostent *poHostEnt = gethostbyname(addr);
			if( NULL == poHostEnt )
			{
				return false;
			}
			memcpy(&(sockaddr->sin_addr), poHostEnt->h_addr, poHostEnt->h_length);
		}
	}
	return true;
}

//============================================================================
bool VxSktBase::bindSocket( struct addrinfo * poResultAddr )	
{
	m_rcLastError = 0;
	if( SOCKET_ERROR == bind( m_oSocket, poResultAddr->ai_addr, (int)poResultAddr->ai_addrlen ) )
	{
		// connect error
		m_eSktCallbackReason = eSktCallbackReasonConnectError;
		m_rcLastError = VxGetLastError();
		log_msg( LOG_ERROR, "VxSktBase::UdpOpen: bind error %d %s\n", 
			m_rcLastError,
			DescribeSktError( m_rcLastError ) );
		m_pfnRecieve( this );
		return false;
	}
	return true;
}

//============================================================================
bool VxSktBase::setReuseSocket( bool bReuse )
{
	m_rcLastError = 0;
	// allow same port to be reused
	int iReusePort = true;
	if( SOCKET_ERROR == setsockopt( m_oSocket, SOL_SOCKET,SO_REUSEADDR, (const char *)&iReusePort, sizeof(iReusePort)) ) 
	{
		m_rcLastError = VxGetLastError();
		log_msg( LOG_ERROR,  "VxSktBase::setReuseSocket error %d\n", m_rcLastError );
	}
	return ( 0 == m_rcLastError ) ? true : false;
}

//============================================================================
bool VxSktBase::isIPv6Address(const char *addr)
{
	if( NULL == addr )
	{
		return false;
	}
	std::string addrStr = addr;
	if (addrStr.find(":") != std::string::npos)
	{
		return true;
	}
	return false;
}

//============================================================================
int VxSktBase::getIPv6ScopeID(const char *addr)
{
	if( false == isIPv6Address( addr ) )
	{
		return 0;
	}
	std::string addrStr = addr;
	int pos = (int)addrStr.find("%");
	if (pos == (int)std::string::npos)
	{
		return 0;
	}
	std::string scopeStr = addrStr.substr(pos+1, addrStr.length());
	return atoi(scopeStr.c_str());
}

//============================================================================
const char *VxSktBase::stripIPv6ScopeID( const char *addr, std::string &buf )
{
	std::string addrStr = addr;
	if( true == isIPv6Address( addr ) ) 
	{
		unsigned int pos = (int)addrStr.find("%");
		if (pos != std::string::npos)
			addrStr = addrStr.substr(0, pos);
	}
	buf = addrStr;
	return buf.c_str();
}

//============================================================================
bool VxSktBase::joinMulticastGroup( VxInetAddress& oLclAddress, const char *mcastAddr )
{
	std::string strLclIp = oLclAddress.toStdString();
	bool bResult = true;
	struct addrinfo hints;
	memset(&hints, 0, sizeof(hints));
	hints.ai_flags= AI_NUMERICHOST | AI_PASSIVE;

	struct addrinfo * mcastAddrInfo; 
	struct addrinfo * lclAddrInfo;
	if( 0 != getaddrinfo(mcastAddr, NULL, &hints, &mcastAddrInfo) )
	{
		m_rcLastError = VxGetLastError();
		log_msg( LOG_ERROR, "VxSktBase::joinGroup unable to get multicast address info error %d\n", m_rcLastError ); 
		return false;
	}
	if (getaddrinfo( m_strLclIp.c_str(), NULL, &hints, &lclAddrInfo ) != 0) 
	{
		log_msg( LOG_ERROR, "VxSktBase::joinGroup unable to get local address info error %d\n", m_rcLastError ); 
		freeaddrinfo(mcastAddrInfo);
		return false;
	}

	if( isIPv6Address( strLclIp.c_str() ) ) 
	{
		struct ipv6_mreq ipv6mr;
		struct sockaddr_in6 toaddr6, ifaddr6;
		memcpy(&toaddr6, mcastAddrInfo->ai_addr, sizeof(struct sockaddr_in6));
		memcpy(&ifaddr6, lclAddrInfo->ai_addr, sizeof(struct sockaddr_in6));
		ipv6mr.ipv6mr_multiaddr = toaddr6.sin6_addr;	
		int iScopeID = getIPv6ScopeID( strLclIp.c_str() );
		ipv6mr.ipv6mr_interface = iScopeID; //if_nametoindex

		if( 0 != setsockopt( m_oSocket, IPPROTO_IPV6, IPV6_MULTICAST_IF, (char *)&iScopeID, sizeof( iScopeID )) )
		{
			m_rcLastError = VxGetLastError();
			log_msg( LOG_ERROR, "VxSktBase::joinGroup set mulicast if error %d\n", m_rcLastError ); 
			bResult = false;
		}
		if( 0 != setsockopt( m_oSocket, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&ipv6mr, sizeof(ipv6mr) ) )
		{
			m_rcLastError = VxGetLastError();
			log_msg( LOG_ERROR, "VxSktBase::joinGroup join group error %d\n", m_rcLastError ); 
			bResult = false;
		}
	}
	else 
	{
		struct ip_mreq ipmr;
		struct sockaddr_in toaddr, ifaddr;
		memcpy(&toaddr, mcastAddrInfo->ai_addr, sizeof(struct sockaddr_in));
		memcpy(&ifaddr, lclAddrInfo->ai_addr, sizeof(struct sockaddr_in));
		memcpy(&ipmr.imr_multiaddr.s_addr, &toaddr.sin_addr, sizeof(struct in_addr));
		memcpy(&ipmr.imr_interface.s_addr, &ifaddr.sin_addr, sizeof(struct in_addr));

		//if( 0 != setsockopt( m_oSocket, IPPROTO_IP, IP_MULTICAST_IF, (char *)&ipmr.imr_interface.s_addr, sizeof(struct in_addr) ) )
		//{
		//	m_rcLastError = VxGetLastError();
		//	log_msg( LOG_ERROR, "VxSktBase::joinGroup set mulicast if error %d\n", m_rcLastError ); 
		//	bResult = false;
		//}
		if( 0 != setsockopt( m_oSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&ipmr, sizeof(ipmr) ) )
		{
			m_rcLastError = VxGetLastError();
			log_msg( LOG_ERROR, "VxSktBase::joinGroup set add membership error %d\n", m_rcLastError ); 
			bResult = false;
		}
	}
	setTTL( 255 );
	setAllowLoopback( true );
	freeaddrinfo(mcastAddrInfo);
	freeaddrinfo(lclAddrInfo);

	return bResult;
}

//============================================================================
void VxSktBase::setTTL( U8 ttl )
{
	if( 0 != setsockopt( m_oSocket, 0, 10, (char *)&ttl, sizeof( ttl )) )
	{
		m_rcLastError = VxGetLastError();
		log_msg( LOG_ERROR, "VxSktBase::setTTL error %d\n", m_rcLastError ); 
	}
}

//============================================================================
void VxSktBase::setAllowLoopback( bool allowLoopback )
{
	U32 loopbackOption = allowLoopback;

	if( 0 != setsockopt( m_oSocket, 0, 11, (char *)&loopbackOption, sizeof( loopbackOption )) )
	{
		m_rcLastError = VxGetLastError();
		log_msg( LOG_ERROR, "VxSktBase::setAllowLoopback error %d\n", m_rcLastError ); 
	}
}

//============================================================================
void VxSktBase::setAllowBroadcast( bool allowBroadcast )
{
	U32 broadcastOption = allowBroadcast;

	if( 0 != setsockopt( m_oSocket, 0xffff, 32, (char *)&broadcastOption, sizeof( broadcastOption )) )
	{
		m_rcLastError = VxGetLastError();
		log_msg( LOG_ERROR, "VxSktBase::setAllowBroadcast error %d\n", m_rcLastError ); 
	}
}

//============================================================================
//! Set Receive Callback
void VxSktBase::SetRecieveCallback( VX_SKT_CALLBACK pfnRecieve )
{
	m_pfnRecieve = pfnRecieve;
}

//============================================================================
//! Set Transmit Callback ( optional for transmit statistics )
void VxSktBase::SetTransmitCallback( VX_SKT_CALLBACK pfnTransmit )
{
	m_pfnTransmit = pfnTransmit;
}

//============================================================================-
//! set socket to blocking or not
RCODE VxSktBase::SetSktBlocking( BOOL bBlock )
{
	//log_msg( LOG_INFO, "VxSktBase::SetSktBlocking %d skt %d\n", bBlock, this->m_iSktId );
	if( bBlock )
	{
		// set to non blocking
		#ifdef WIN_PLATFORM
            int iResult;
			u_long s32NonBlock = false; 
			iResult = ioctlsocket( m_oSocket, FIONBIO, &s32NonBlock );
			if( iResult != 0 )
			{
				m_rcLastError = VxGetLastError();
				log_msg( LOG_ERROR, "VxSktBase::SetSktBlocking skt %d ioctlsocket error %s\n", m_iSktId, DescribeSktError( m_rcLastError ) );
				return m_rcLastError;
			}
		#else
// this works in linux but not android
//			int iFlags = fcntl(m_oSocket, F_GETFL, NULL);
//			if( 0 >= iFlags )
//			{
//				log_msg( LOG_ERROR, "VxSktBase::SetSktBlocking fcntl error\n" );
//				return -1;
//			}
//           fcntl( m_oSocket, F_SETFL, iFlags | O_NONBLOCK);

			// this works in android
			int mode = 0;
			ioctl( m_oSocket, FIONBIO, &mode );
		#endif
	}
	else
	{
		// set to non blocking
		#ifdef WIN_PLATFORM
			u_long s32NonBlock = true; 
            int iResult;
			iResult = ioctlsocket( m_oSocket, FIONBIO, &s32NonBlock );
			if( iResult != 0 )
			{
				m_rcLastError = VxGetLastError();
				log_msg( 0, "VxSktBase::SetSktBlocking ioctlsocket error %s\n", DescribeSktError( m_rcLastError ) );
				return -1;
			}
		#else
// this works in linux but not android
//			int iFlags = fcntl(m_oSocket, F_GETFL, NULL);
//			if( 0 >= iFlags )
//			{
//				log_msg( LOG_ERROR, "VxSktBase::SetSktBlocking fcntl error\n" );
//				return -1;
//			}
//            iFlags &= ~O_NONBLOCK;
//            fcntl( m_oSocket, F_SETFL, iFlags);

			// this works in android
			int mode = 1;
			ioctl( m_oSocket, FIONBIO, &mode );
		#endif
	}
	//log_msg( LOG_INFO, "VxSktBase::SetSktBlocking %d skt %d done\n", bBlock, this->m_iSktId );
	return 0;
}

//============================================================================
//! Connect to 
RCODE VxSktBase::ConnectTo(	VxInetAddress&	oLclIp,
							const char *	pIpUrlOrIp,				// remote ip 
							U16				u16Port,				// port to connect to
							int				iTimeoutMilliSeconds)	// milli seconds before connect attempt times out
{
	m_oLclIp = oLclIp;
	m_oLclIp.toStdString( m_strLclIp );

	//VxVerifyCodePtr( m_pfnRecieve );
	if( IsConnected() )
	{
#ifdef DEBUG_SKTS
		log_msg( LOG_ERROR, "VxSktBase::ConnectTo: skt %d connect attempt on already connected socket\n", m_iSktId );
#endif // DEBUG_SKTS
		vx_assert( false );
		return -1;
	}
	m_bIsConnected = false;
	// kill previous thread if any
	m_oSktRxThread.KillThread();

#ifdef DEBUG_SKTS
	log_msg( LOG_SKT, "VxSktBase::ConnectTo: skt %d ip %s port %d\n", this->m_iSktId, pIpUrlOrIp, u16Port );
#endif // DEBUG_SKTS
	//resolve url
	std::string strUrlFile;
	U16 u16ReturnedPort;
	bool bResolved = VxResolveHostToIp(	pIpUrlOrIp,			//web name to resolve
										m_strRmtIp,
										u16ReturnedPort );		//return dotted string ( must be at least 16 bytes long )
	if( false == bResolved )
	{
		m_rcLastError = -1;
		m_eSktCallbackReason = eSktCallbackReasonConnectError;
#ifdef DEBUG_SKTS
		log_msg( 0, "VxSktBase::ConnectTo: skt %d could not resolve url %s\n", m_iSktId, pIpUrlOrIp );
#endif // DEBUG_SKTS
		// cannot do callback except in thread because may cause mutex deadlock
		//m_pfnRecieve( this );
		return m_rcLastError;
	}
	m_oRmtIp.setIp( m_strRmtIp.c_str() );
	m_oRmtIp.setPort( u16Port );
	m_eSktCallbackReason	= eSktCallbackReasonConnecting;
	m_iConnectTimeout		= iTimeoutMilliSeconds;

	RCODE rc = DoConnectTo();
	if( rc )
	{
		//log_msg( LOG_INFO, "DoConnectTo returned error %d\n", rc );
		return rc;
	}
	// make a useful thread name
	std::string strThreadName;
	StdStringFormat( strThreadName, "VxSktBaseTCP%d", m_iSktId );
	m_oSktRxThread.StartThread( (VX_THREAD_FUNCTION_T)SktBaseRecieveThreadFunc, this, strThreadName.c_str() );
#ifdef DEBUG_SKTS
	log_msg( LOG_SKT,  "skt %d connected to %s:%d\n", m_iSktId, pIpUrlOrIp, u16Port );
#endif // DEBUG_SKTS
	return 0;
}

//============================================================================
//! Do connect to from thread
RCODE VxSktBase::DoConnectTo( void )
{
	//log_msg( LOG_INFO, "VxSktBase::DoConnectTo: skt %d handle %d connect to %s port %d\n",
	//	m_iSktId,
	//	m_oSocket,
	//	m_strRmtIp.c_str(),
	//	m_oRmtIp.getPort() );

	m_oSocket = VxConnectTo( m_oLclIp, m_oRmtIp, m_oRmtIp.getPort(), SKT_CONNECT_TIMEOUT );

	if( INVALID_SOCKET != m_oSocket )
	{
		//log_msg( LOG_INFO, "VxSktBase::DoConnectTo: getting remote ip skt %d handle %d connect to %s port %d\n",
		//	m_iSktId,
		//	m_oSocket,
		//	m_strRmtIp.c_str(),
		//	m_oRmtIp.getPort() );

		// get the peer address
		m_rcLastError = DoGetRmtAddress();
		if( m_rcLastError )
		{
			//log_msg( LOG_INFO, "VxSktBase::DoConnectTo: skt %d handle %d connect to %s get remote ip error %s\n",
			//	m_iSktId,
			//	m_oSocket,
			//	m_strRmtIp.c_str(),
			//	DescribeSktError( m_rcLastError ) );
			// we connected.. dont error out just cause getpeername failed
			m_rcLastError = 0;
			//m_eSktCallbackReason = eSktCallbackReasonConnectError;
			//return m_rcLastError;
		}
#ifdef DEBUG_SKTS
		log_msg( LOG_INFO, "VxSktBase::DoConnectTo: SUCCESS skt %d handle %d connect to %s port %d\n",
			m_iSktId,
			m_oSocket,
			m_strRmtIp.c_str(),
			m_oRmtIp.getPort() );
#endif // DEBUG_SKTS
		m_bIsConnected = true;
		return 0;
	}
	else
	{
		m_bIsConnected = false;
#ifdef DEBUG_SKTS
		log_msg( LOG_ERROR, "VxSktBase::DoConnectTo: FAILED INVALID_SKT skt %d connect to %s port %d\n",
			m_iSktId,
			m_strRmtIp.c_str(),
			m_oRmtIp.getPort() );
#endif // DEBUG_SKTS
		return -1;
	}
}

//============================================================================
//! CloseSkt the socket
RCODE VxSktBase::CloseSkt( int iInstance, BOOL bFlushThenClose )
{
	if( (0 >= decRefCnt()) || m_bClosingFromRxThread || m_bClosingFromDestructor )
	{
#ifdef DEBUG_SKTS
		log_msg( LOG_SKT,  "skt %d CloseSkt instance %d to %s:%d\n", m_iSktId, iInstance, m_strRmtIp.c_str(), m_oRmtIp.getPort() );
#endif // DEBUG_SKTS
		m_oSktRxThread.Abort( true );
		m_oSktTxThread.Abort( true );
		m_oSktTxSemaphore.Signal();
		m_bIsConnected = false;
		if( INVALID_SOCKET != m_oSocket )
		{
			log_msg( LOG_INFO, "VxSktBase::CloseSkt: Skt %d inst %d ip %s thread %d\n", m_iSktId, iInstance, m_strRmtIp.c_str(), VxGetCurrentThreadId() );
			SOCKET oSocket = m_oSocket; 
			m_oSocket = INVALID_SOCKET;

			if( bFlushThenClose )
			{
				VxFlushThenCloseSkt( oSocket );
			}
			else
			{
				VxCloseSktNow( oSocket );
			}
			//log_msg( LOG_INFO, "VxSktBase::CloseSkt: Skt %d handle %d close done\n", m_iSktId, oSocket );
		}

		// if thread tries to suicide then problems occurs because thread cannot
		// exit while attempting to kill itself
		if( (false == m_bClosingFromRxThread ) &&
			( m_oSktRxThread.GetThreadId() != VxGetCurrentThreadId() ) )
		{
			//log_msg( LOG_INFO, "VxSktBase::CloseSkt: Skt %d killing thread %d\n", m_iSktId, m_oSktRxThread.GetThreadId() );
			m_oSktRxThread.KillThread();
		}
		//log_msg( LOG_INFO, "VxSktBase::CloseSkt: Skt %d done\n", m_iSktId );
		return 0;
	}
	else
	{
#ifdef DEBUG_SKTS
		log_msg( LOG_INFO, "VxSktBase::CloseSkt: Skt %d inst %d not zero ref count\n", m_iSktId, iInstance );
#endif // DEBUG_SKTS
	}
	return 0;
}

//============================================================================
//! send data without encrypting
RCODE VxSktBase::SendData(	const char *	pData,					// data to send
							int				iDataLen,				// length of data	
							BOOL			bDisconnectAfterSend,	// if true disconnect after data is sent
                            int				/*iSendTimeoutSeconds*/ )	// timeout attempt to send
{
	if( false == IsConnected() )
	{
#ifdef DEBUG_SKTS
		log_msg(  0, "VxSktBase::SendData: Attempted send on disconnected skt %d\n", this->m_iSktId );
#endif // DEBUG_SKTS
		return -1;
	}
#ifdef DEBUG_SKTS
	log_msg( LOG_SKT, "skt %d SendData length %d to %s:%d\n", m_iSktId, iDataLen, m_strRmtIp.c_str(), m_oRmtIp.getPort() );
#endif // DEBUG_SKTS
	if( INVALID_SOCKET != m_oSocket )
	{
		int iSentLen;
		while( TRUE )
		{
			iSentLen = send( m_oSocket, (const char *)pData, iDataLen, 0);
			if( 0 > iSentLen )
			{
				// error
				m_rcLastError = VxGetLastError();
				if( 0 == m_rcLastError )
				{
					m_rcLastError = iSentLen;
				}
#ifdef DEBUG_SKTS
				log_msg(  0, "VxSktBase::SendData: Skt %d Handle %d Error %s\n", 
									m_iSktId, 
									m_oSocket, 
									DescribeSktError( m_rcLastError ) );
#endif // DEBUG_SKTS
				if( bDisconnectAfterSend )
				{
					CloseSkt(27460, true);
				}
				return m_rcLastError;
			}
			pData = pData + iSentLen;
			iDataLen -= iSentLen;
			TxedPkt( iSentLen );
			m_iLastTxLen = iSentLen;
			if( m_pfnTransmit )
			{
				m_pfnTransmit( this );
			}
			if( 0 >= iDataLen )
			{
				// all done
				if( bDisconnectAfterSend )
				{
					CloseSkt( 3298, true );
				}
				return 0;
			}
			// sleep and try again
			VxSleep( 20 );
		}
	}
#ifdef DEBUG_SKTS
	else
	{
		log_msg( LOG_SKT, "INVALID SKT skt %d SendData length %d to %s:%d\n", m_iSktId, iDataLen, m_strRmtIp.c_str(), m_oRmtIp.getPort() );
	}
#endif // DEBUG_SKTS

	if( bDisconnectAfterSend )
	{
		CloseSkt( 3299, true );
	}
	return -1;
}

//============================================================================
//! encrypt then send data using session crypto
RCODE VxSktBase::TxEncrypted(	const char *	pDataIn, 			// data to send
								int				iDataLen,		// length of data
								BOOL			bDisconnect )	// if true disconnect after send
{
	vx_assert( pDataIn );
	vx_assert( iDataLen );
	vx_assert( 0 == (iDataLen & 0x0f) );
	vx_assert( m_oTxCrypto.IsKeyValid() );
	// make copy of data so data is not destroyed
	unsigned char * pu8Data = new unsigned char[ iDataLen ];
	memcpy( pu8Data, pDataIn, iDataLen );
	// encrypt
	RCODE rc =	m_oTxCrypto.Encrypt( pu8Data, iDataLen );
	if( 0 == rc )
	{
		// send
		rc = this->SendData( (char *)pu8Data, iDataLen );
	}
#ifdef DEBUG_SKTS
	if( rc )
	{
		log_msg( 0, "VxSktBase::TxEncrypted: crypto error %d\n", rc );
	}
#endif // DEBUG_SKTS
	delete pu8Data;
	if( bDisconnect )
	{
		VxSleep( 50 );
		CloseSkt();
	}
	return rc;
}

//============================================================================
//! encrypt with given key then send.. does not affect session crypto
RCODE VxSktBase::TxEncrypted(	VxKey *			poKey,			// key to encrypt with
								const char *	pDataIn,			// data to send
								int				iDataLen,		// length of data
								BOOL			bDisconnect )	// if true disconnect after send
{
	vx_assert( pDataIn );
	vx_assert( iDataLen );
	vx_assert( 0 == (iDataLen & 0x0f) );
	// make copy of data so data is not destroyed
	char * pData = new char[ iDataLen ];
	memcpy( pData, pDataIn, iDataLen );
	// encrypt
	VxSymEncrypt( poKey, (char *)pData, iDataLen );
	// send
	RCODE rc = this->SendData( (char *)pData, iDataLen );
	delete pData;
	if( bDisconnect )
	{
		CloseSkt();
	}
#ifdef DEBUG_SKTS
	if( rc )
	{
		log_msg( LOG_ERROR, "VxSktBase::TxEncrypted: error %d\n", rc );
	}
#endif // DEBUG_SKTS
	return rc;
}

//============================================================================
//! encrypt then send packet using session crypto
RCODE VxSktBase::TxPacket(	VxPktHdr *		poPkt, 			// packet to send
							BOOL			bDisconnect )	// if true disconnect after send
{
	return TxEncrypted( (const char *)poPkt, poPkt->m_u16PktLen, bDisconnect );
}

//============================================================================
//! decrypt as much as possible in receive buffer
RCODE VxSktBase::DecryptRecieveData( void )
{
	if( false == IsRxEncryptionKeySet() )
	{
		// no key to decrypt with
		return -1;
	}
    U32 u32Datalen = GetSktBufDataLen();
	// truncate to 16 byte boundary
	u32Datalen = u32Datalen & 0xfffffff0;
	if( u32Datalen )
	{
		vx_assert( u32Datalen >= m_u32RxDecryptedLen );
		U32 u32LenToDecrypt = u32Datalen - m_u32RxDecryptedLen;
		if( u32LenToDecrypt )
		{
			m_oRxCrypto.Decrypt( &m_pau8SktBuf[ m_u32RxDecryptedLen ], u32LenToDecrypt );
			m_u32RxDecryptedLen += u32LenToDecrypt;
		}
	}
	return 0;
}

//============================================================================
//! return the socket type
ESktType VxSktBase::GetSktType( void )
{
	return m_eSktType;
}

//============================================================================
//! return true if is connected
BOOL VxSktBase::IsConnected( void )
{
	if( INVALID_SOCKET == m_oSocket )
	{
		m_bIsConnected = false;
	}
	return m_bIsConnected;
}

//============================================================================
//! get the sockets peer connection ip address as net order U32
RCODE VxSktBase::GetRemoteIp(	VxInetAddress &u32RetIp,		// return ip
								U16 &u16RetPort )	// return port
{
	u32RetIp = m_oRmtIp;
	u16RetPort = m_oRmtIp.getPort();
	return 0;
}

//============================================================================
//! simpler version of GetRemoteIp returns ip as net order U32
RCODE VxSktBase::GetRemoteIp( VxInetAddress &u32RetIp )			// return ip
{
	u32RetIp = m_oRmtIp;
	return 0;
}

//============================================================================
//! get remote ip as string
const char * VxSktBase::GetRemoteIp( void )
{
	return m_strRmtIp.c_str();
}

//============================================================================
//! get local ip as string
const char * VxSktBase::GetLocalIp( void )
{
	return m_strLclIp.c_str();
}

//============================================================================
//! used internally to get the remote ip address and set it in the vars
RCODE VxSktBase::DoGetRmtAddress( void )
{
	if( INVALID_SOCKET != m_oSocket )
	{
		struct sockaddr_storage oSktAddr;
		// Get the IP address of the the remote side of connection
        socklen_t iSktAddrLen = sizeof( oSktAddr );
		if( getpeername( m_oSocket, (struct sockaddr *)&oSktAddr, &iSktAddrLen ) )
		{
			// error occurred
			int rc = VxGetLastError();
#ifdef DEBUG_SKTS
			log_msg( LOG_SKT, "VxSktBase::DoGetRmtAddress skt %d error %d %s\n", m_iSktId, rc, DescribeSktErr( rc ) );
#endif // DEBUG_SKTS
			return rc;
		}
		SetRmtAddress( oSktAddr );
		return 0;
	}
#ifdef DEBUG_SKTS
	else
	{
		log_msg( LOG_SKT, "VxSktBase::DoGetRmtAddress skt %d INVALID_SOCKET\n", m_iSktId );
	}
#endif // DEBUG_SKTS
	return -1;
}

//============================================================================
void VxSktBase::SetRmtAddress( struct sockaddr_storage& oSktAddr )
{
	m_oRmtIp.setIpAndPort( oSktAddr );
	m_strRmtIp = m_oRmtIp.toStdString();
#ifdef DEBUG_SKTS
	log_msg( LOG_SKT, "SetRmtAddress ip %s port %d\n", m_strRmtIp.c_str(), m_oRmtIp.getPort() );
#endif // DEBUG_SKTS
}

//============================================================================
void VxSktBase::SetRmtAddress( struct sockaddr_in& oSktAddrIn )
{
	struct sockaddr_storage oSktAddr;
	memset( &oSktAddr, 0, sizeof( oSktAddr ) );
	memcpy( &oSktAddr, &oSktAddrIn, sizeof( oSktAddrIn ) );

	SetRmtAddress( oSktAddr );
}

//============================================================================
void VxSktBase::SetLclAddress( struct sockaddr_storage& oSktAddr )
{
	m_oLclIp.setIpAndPort( oSktAddr );
	m_strLclIp = m_oLclIp.toStdString();
}

//============================================================================
//! fire up receive thread
RCODE VxSktBase::StartRecieveThread( const char * pThreadName )
{
	m_oSktRxThread.KillThread();
	m_oSktRxThread.StartThread( (VX_THREAD_FUNCTION_T)SktBaseRecieveThreadFunc, this, pThreadName );
	return 0;
}

//============================================================================
//! thread function to receive data from socket
U32 SktBaseRecieveThreadFunc( void * pvContext )
{
	VxThread * poThread = (VxThread *)pvContext;
	poThread->SetIsThreadRunning( true );
	VxSktBase * poSkt = (VxSktBase *)poThread->GetUserParam();
	//log_msg(  0, "SktBaseRecieveThreadFunc: start skt %d EINPROGRESS %d\n", poSkt->m_iSktId, EINPROGRESS );
	char as8Buf[ 0x8000 ];
    int iDataLen = 0;
    //int iBufferAlmostFull = poSkt->GetSktBufSize() - poSkt->GetSktBufSize() / 10;
	struct sockaddr_storage oAddr;
	BOOL bIsUdpSkt = true;
	int iSktId = poSkt->m_iSktId;

	if( eSktTypeTcpConnect == poSkt->GetSktType() ||
		eSktTypeTcpAccept == poSkt->GetSktType() )
	{
		bIsUdpSkt = false;
		if( eSktTypeTcpConnect == poSkt->GetSktType()  )
		{
			// we couldn't do callbacks in connect function ( mutex issues ) so
			// do the callback now
			poSkt->m_eSktCallbackReason = eSktCallbackReasonConnecting;
			poSkt->m_pfnRecieve( poSkt );
		}
	}

	if(	( poThread->isAborted() ) ||
		( INVALID_SOCKET == poSkt->m_oSocket ) )
	{
		// something has already happened to the connection
		//! Thread calls this just before exit
		poThread->ThreadAboutToExit();

		return 0;
	}

	poSkt->m_bIsConnected = true;
	poSkt->m_eSktCallbackReason = eSktCallbackReasonConnected;
	poSkt->m_pfnRecieve( poSkt );
	poSkt->m_eSktCallbackReason = eSktCallbackReasonData;

	//log_msg( LOG_SKT, "SktBaseRecieveThreadFunc: set blocking true\n" );
	poSkt->SetSktBlocking( true );

	while(	( false == poThread->isAborted() ) && 
			( INVALID_SOCKET != poSkt->m_oSocket ) &&
			( eSktCallbackReasonData == poSkt->m_eSktCallbackReason ) )
	{
		if(  false == poSkt->IsConnected() ) 
		{
#ifdef DEBUG_SKTS
			log_msg( LOG_SKT, "SktBaseRecieveThreadFunc: skt %d no longer connected\n", poSkt->m_iSktId );
#endif // DEBUG_SKTS
			break;
		}
		int iAttemptLen = poSkt->GetSktBufFreeSpace();
		vx_assert( iAttemptLen >= 0 );
		vx_assert( iAttemptLen <= poSkt->m_iSktBufSize );
		if( iAttemptLen >= (int)sizeof( as8Buf ) )
		{
			iAttemptLen = (int)sizeof( as8Buf ) - 16;
		}
		//log_msg( LOG_SKT, "SktBaseRecieveThreadFunc: iAttemptLen %d\n", iAttemptLen );

		if( bIsUdpSkt )
		{
			if( 4 == iSktId )
			{
				iSktId = iSktId;
			}

			memset( &oAddr, 0, sizeof( struct sockaddr_storage ) );
			socklen_t iSktAddrLen = sizeof( struct sockaddr_storage );
			if(  poSkt->m_oLclIp.isIPv4() )
			{
				iSktAddrLen = sizeof( struct sockaddr_in );
				((struct sockaddr_in *)&oAddr)->sin_family = AF_INET;
			}
			else
			{
				iSktAddrLen = sizeof( struct sockaddr_in6 );
				((struct sockaddr_in6 *)&oAddr)->sin6_family = AF_INET6;
			}

			iDataLen = recvfrom(	poSkt->m_oSocket,	// socket
									as8Buf,				// buffer to read into
									iAttemptLen,		// length of buffer space
									0,					// flags
									(struct sockaddr *)&oAddr, // source address
									&iSktAddrLen );		// size of address structure
			//log_msg( LOG_DEBUG, "udp skt %d rx from ", iSktId );

			//if( 1 == iSktId )
			//{
			//	iSktId = iSktId;
			//}
			//if( 2 == iSktId )
			//{
			//	iSktId = iSktId;
			//}
			//if( 3 == iSktId )
			//{
			//	iSktId = iSktId;
			//}

			if( INVALID_SOCKET == poSkt->m_oSocket )
			{
				// has been closed
				poThread->ThreadAboutToExit();
				return 0;
			}
			//log_msg( LOG_INFO, "SktBaseRecieveThreadFunc: udp recvfrom skt %d len %d port %d\n", poSkt->m_iSktId, iDataLen, poSkt->m_oRmtIp.getPort() );
			if( iDataLen > 0 )
			{
				if( 2 == iSktId )
				{
					iSktId = iSktId;
				}
				poSkt->m_oRmtIp.m_u16Port = poSkt->m_oRmtIp.setIp( oAddr );
				poSkt->m_strRmtIp = poSkt->m_oRmtIp.toStdString();
				if( poSkt->m_oRxKey.IsKeySet() )
				{
					if( false == poSkt->m_oRxKey.IsValidDataLen( iDataLen ) )
					{
						// throw away the data because not valid length
						continue;
					}
					// set encryption context
					poSkt->m_oRxCrypto.ImportKey( &poSkt->m_oRxKey );
				}
			}
		}
		else
		{
			//log_msg( LOG_SKT, "SktBaseRecieveThreadFunc: recv skt handle %d attempt len %d\n", poSkt->m_oSocket, iAttemptLen );

			iDataLen = recv(		poSkt->m_oSocket,	// socket
									as8Buf,				// buffer to read into
									iAttemptLen,		// length of buffer space
									0 );				// flags
			if( INVALID_SOCKET == poSkt->m_oSocket )
			{
				// has been closed
				poThread->ThreadAboutToExit();
				return 0;
			}
#ifdef DEBUG_SKTS
			if( poSkt->IsTcpSocket() )
			{
				log_msg( LOG_SKT, "SktBaseRecieveThreadFunc: tcp recv skt %d len %d attempt len %d\n", poSkt->m_iSktId, iDataLen, iAttemptLen );
			}
#endif // DEBUG_SKTS
		}

		if( poThread->isAborted() || 
			( eSktCallbackReasonData != poSkt->m_eSktCallbackReason ) || 
			( INVALID_SOCKET == poSkt->m_oSocket ) ||
 			( iDataLen <= 0 ) )
		{
			if( poThread->isAborted() || ( INVALID_SOCKET == poSkt->m_oSocket ) )
			{
				// normal close or shutdown
#ifdef DEBUG_SKTS
				log_msg( LOG_SKT, "SktBaseRecieveThreadFunc: skt %d closed or aborted\n", poSkt->m_oSocket );
#endif // DEBUG_SKTS

				poSkt->m_rcLastError = 0;
				break;
			}
			// socket error occurred
			poSkt->m_rcLastError = VxGetLastError();
			//log_msg( LOG_INFO, "SktBaseRecieveThreadFunc: skt %d error %d\n", poSkt->m_iSktId, poSkt->m_rcLastError );
			if( 0 == poSkt->m_rcLastError )
			{
				poSkt->m_rcLastError = -1;
			}
			if( ( iDataLen < 0 ) && ((11 == poSkt->m_rcLastError) || ( EINPROGRESS == poSkt->m_rcLastError )) )
			{
				// try again
#ifdef DEBUG_SKTS
				log_msg( LOG_INFO, "SktBaseRecieveThreadFunc: skt %d trying again\n" );
#endif // DEBUG_SKTS
				VxSleep( 100 );
				continue;
			}
			break;
		}
		if( iDataLen > 0 )
		{
			//if( poSkt->IsTcpSocket() )
			//{
			//	log_msg( LOG_SKT,  "skt %d Received length %d from %s:%d\n", 
			//		poSkt->m_iSktId, 
			//		iDataLen, 
			//		poSkt->m_strRmtIp.c_str(), 
			//		poSkt->m_oRmtIp.getPort() );
			//}
			memcpy( poSkt->GetSktWriteBuf(), as8Buf, iDataLen );
			poSkt->SktBufAmountWrote( iDataLen );
			if( false == poSkt->IsUdpSocket() )
			{
				// decrypt as much as possible
				poSkt->DecryptRecieveData();
			}
			poSkt->m_iLastRxLen = iDataLen;
			poSkt->RxedPkt( iDataLen );
			// call back user with the good news.. we have data
			//log_msg( LOG_INFO, "SktBaseRecieveThreadFunc: skt %d receiving len %d\n", poSkt->m_iSktId, iDataLen );
			poSkt->m_pfnRecieve( poSkt );
		}
	}
	if( eSktCallbackReasonNewMgr != poSkt->GetCallbackReason() )
	{
		if( poSkt->m_rcLastError )
		{
#ifdef DEBUG_SKTS
			log_msg( LOG_INFO, "SktBaseRecieveThreadFunc: skt %d error %d\n", poSkt->m_iSktId, poSkt->m_rcLastError );
#endif // DEBUG_SKTS
			// we had a error
			poSkt->m_eSktCallbackReason = eSktCallbackReasonError;
			poSkt->m_pfnRecieve( poSkt );
		}
		if( false == poThread->isAborted() )
		{
			// we are closing due to error .. not because user called close
			poSkt->m_bClosingFromRxThread = true;
		}
#ifdef DEBUG_SKTS
		log_msg( LOG_INFO, "SktBaseRecieveThreadFunc: skt %d closing func 0x%x\n", poSkt->m_iSktId, poSkt->m_pfnRecieve );
#endif // DEBUG_SKTS
		poSkt->m_eSktCallbackReason = eSktCallbackReasonClosing;
		poSkt->m_pfnRecieve( poSkt );
		poSkt->m_eSktCallbackReason = eSktCallbackReasonClosed;
		if( poSkt->m_poChainedSkt )
		{
			poSkt->m_poChainedSkt->CloseSkt(444);
		}
		// the received closed will delete us.. so do about to exit now
		poThread->ThreadAboutToExit(false);
		poSkt->m_pfnRecieve( poSkt );
#ifndef WIN_PLATFORM
		// linux exit thread
		pthread_exit(0);
#endif // WIN_PLATFORM
		return 0;
	}
#ifdef DEBUG_SKTS
	log_msg( LOG_INFO, "SktBaseRecieveThreadFunc: skt %d exiting\n", iSktId );
#endif // DEBUG_SKTS
	//! Thread calls this just before exit
	poThread->ThreadAboutToExit();

	return 0;
}

